/*
    多路转接之select
*/
#include <iostream>
#include <cstring>
#include <sys/select.h>
#include <unistd.h>
#include "socket.hpp"

class SelectServer
{
private:
    const static int N = sizeof(fd_set) * 8;
    const static int default_fd_val = -1;

    // 调试函数（打印合法rfds）
    void print_leg_rfds()
    {
        for(int i = 0; i < N; i++)
        {
            if(_fd_arry[i] != default_fd_val)
                std::cout << _fd_arry[i] << " ";
        }
        std::cout << "\n";
    }

    // 处理新的tcp链接
    void acceptListen()
    {
        // 1、获取新链接的fd
        struct sockaddr_in srcaddr;
        int newfd = _listensock->Accept(&srcaddr);
        if(newfd == -1)
            return;

        // 2、新增合法fd
        int i = 0;
        for(; i < N && _fd_arry[i] != default_fd_val; i++);
        if(i < N)
        {
            _fd_arry[i] = newfd;
        }
        else if(i == N)
        {
            LOG(WARNING, "监听fd个数已达上线\n");
        }

        // 调试
        LOG(DEBUG, "accept fd:");
        print_leg_rfds();
    }

    // 处理IO
    void ioServer(int pos)
    {
        char buf[1024];
        int n = recv(_fd_arry[pos], buf, sizeof(buf), 0); // 只能保证一次recv不会阻塞，如果循环读取，那么缓冲区空了，下次读就会阻塞
        if(n > 0)
        {
            std::cout << "fd:" << _fd_arry[pos] << ", client:" << buf << "\n";
            std::string echo_str = "echo:";
            echo_str += buf;
            send(_fd_arry[pos], echo_str.c_str(), echo_str.size(), 0);
        }
        else if(n == 0)
        {
            // 对方关闭
            close(_fd_arry[pos]);
            _fd_arry[pos] = default_fd_val;
            LOG(INFO, "fd: %d, 对方关闭\n", _fd_arry[pos]);
            print_leg_rfds();
        }
        else
        {
            // 出现异常
            close(_fd_arry[pos]);
            _fd_arry[pos] = default_fd_val;
            LOG(INFO, "fd: %d, 异常\n", _fd_arry[pos]);
            print_leg_rfds();
        }
    }

    // 事件派发
    void dispatch(fd_set& rfds)
    {
        for(int i = 0; i < N; i++)
        {
            if(_fd_arry[i] == default_fd_val)
                continue;
            if(FD_ISSET(_fd_arry[i], &rfds)) // 判断要监听的读文件描述符是否在rfds集合中
            {
                if(_fd_arry[i] == _listensock->getsockfd())
                {
                    // 监听套接字就绪，有新的tcp链接建立，处理新链接，此时一定不会阻塞
                    acceptListen();
                }
                else
                {
                    // 普通套接字就绪，处理IO，此时一定不会阻塞
                    ioServer(i);
                }
            }
        }
    }

public:
    SelectServer(uint16_t port):_listensock(std::make_unique<socket_ns::TcpSocket>())
    {
        // 1、创建套接字 2、绑定ip+port
        struct sockaddr_in localaddr;
        memset(&localaddr, 0, sizeof(localaddr));
        localaddr.sin_family = AF_INET;
        localaddr.sin_port = htons(port);
        localaddr.sin_addr.s_addr = INADDR_ANY;
        _listensock->buildServerSocket((struct sockaddr*)&localaddr);

        // 初始化合法的要监听读的文件描述符
        for(int i = 0; i < N; i++)
            _fd_arry[i] = default_fd_val;
        _fd_arry[0] = _listensock->getsockfd();
    }

    void loop()
    {
        while(true)
        {
            // 1、设置要监听读的文件描述符
            fd_set rfds;
            int max_fd_val = default_fd_val;
            FD_ZERO(&rfds);
            for(int i = 0; i < N; i++)
            {
                if(_fd_arry[i] == default_fd_val)
                    continue;
                max_fd_val = max_fd_val < _fd_arry[i] ? _fd_arry[i] : max_fd_val;
                FD_SET(_fd_arry[i], &rfds);
            }

            // 2、设置监听方式
            struct timeval timeout = {3, 0}; // 3秒阻塞轮询

            // 3、监听
            int n = select(max_fd_val+1, &rfds, nullptr, nullptr, &timeout);
            switch(n)
            {
            case 0:
                LOG(INFO, "timeout, continue listen...\n"); //继续循环监听
                break;
            case -1:
                LOG(ERROR, "select error!\n");
                return;
            default:
                LOG(INFO, "events is ready!\n");
                dispatch(rfds); //事件派发
                break;
            }
        }
    }
private:
    std::unique_ptr<socket_ns::Socket> _listensock;
    int _fd_arry[N]; // 记录所有合法的fd，方便每次设置监听读文件描述符集合
};